/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author carlos
 */
import java.util.*;
public class ValuateList {
    private HashMap<String, String> map;
    private ArrayList<String> operators;
    private ArrayList<String> comparators;
    private ArrayList<String> newFunctions;
    private ArrayList<String> functions;
    private FunctionsList func;
    
    public ValuateList(){
        // Añadir los operadores a la lista
        operators = new ArrayList<String>();
        operators.add("+");
        operators.add("-");
        operators.add("*");
        operators.add("/");
        
        // Añadir los comparadores a la lista
        comparators = new ArrayList<String>();
        comparators.add("<");
        comparators.add(">");
        comparators.add("G");           // Grader
        comparators.add("L");           // Lesser
        comparators.add("=");
        
        // Inicializar las newFunctions listas y objetos
        newFunctions = new ArrayList<String>();
        newFunctions.add("s");
        
        functions = new ArrayList<String>();
        
        map = new HashMap<String, String>();
        
        func = new FunctionsList(this);
    }
    
    /**
     * 
     * @param function La función ingresada
     * @return 
     */
    public String getResult(String function){
        boolean comparative = false;
        boolean compResult = false;
        boolean setFunc = false;
        double math = 0;
        String ret = null;
        Queue<String> stackOps = new LinkedList<String>();
        Queue<String> stack2 = new LinkedList<String>();
        
        function = function.substring(1, function.length());
        
        for(int i=0; i<functions.size(); i++)
		{
			String funcN=functions.get(i);
			if(function.contains(funcN))
			{
				ret=func.valuateFunction(function);
				return ret;
			}
		}
		
		if(function.contains("defun"))
		{
			int buffer=function.indexOf(' ');
			function=function.substring(buffer, function.length());
			ret = "";
                        func.setFunctions(function);
			functions.add(ret);
			return ret;
		}
		if(function.contains("setf"))
		{
			int buffer=function.indexOf(' ');
			function="s"+function.substring(buffer, function.length());
		}
		if(function.contains("<=") || function.contains(">=") || function.contains("EQUAL"))
		{
			String s1="";
			if(function.contains("<=")) s1="#";
			if(function.contains(">=")) s1="%";
			if(function.contains("EQUAL")) s1="=";
			
			int buffer=function.indexOf(' ');
			function=s1+function.substring(buffer, function.length());
		}
		
		for(int i=0; i<function.length(); i++)
		{
			char c=function.charAt(i);
			
			if(c==')')
			{
				String pop1=stack2.poll();
				
				if(comparators.contains(pop1))
				{
					comparative=true;
				}
				
				while(!stackOps.isEmpty()){
					
					String pop2=searchInMap(stackOps.poll());
					
					if(newFunctions.contains(pop1))
					{
						String pop2a=stackOps.poll();
						setInMap(pop2, pop2a);
						ret=pop2a;
					}
					else if(comparators.contains(pop1))
					{
						String pop2a=searchInMap(stackOps.poll());
						compResult=compResult && compares(pop1.charAt(0), Double.parseDouble(pop2),Double.parseDouble(pop2a));
						ret=""+compResult+"";
						
						if(ret.compareTo("true")==0||ret.compareTo("false")==0)
						{
							if(ret.compareTo("true")==0)
							{
								ret="T";
							}
							else 
							{
								ret="nil";
							}
						}
						
					}
					else if(operators.contains(pop1))
					{	
						if(!setFunc)
						{
							math=Double.parseDouble(pop2);
							setFunc=true;
						}
						else {
							math=operations(pop1.charAt(0), math,Double.parseDouble(pop2));
							ret=""+math+"";
						}
					}
				}
				comparative=false;
				setFunc=false;
				return ret;
			}
			else if(c=='(')
			{
				function=function.substring(i);
				stackOps.add(""+getResult(function)+"");
				function=function.substring(function.indexOf(')'));
				i=0;
			}
			else if(operators.contains(""+c+"") || comparators.contains(""+c+"") || newFunctions.contains(""+c+""))
			{
				if(c=='*') math=1;

				stack2.add(""+c+"");
			}
			else if(c!=' ')
			{
				String oper="";
				
				while(c!=' ' && c!='(' && c!=')'){

					oper=oper+c;
					i++;
					c=function.charAt(i);	
				}

				stackOps.add(oper);
				i--;
			}
		}
        
        return ret;
    }
    
    /**
     * Funcioń utilizada para aritmética
     * @param op La operación a implementar
     * @param num1 El primer operador
     * @param num2 El segundo operador
     * @return El resultado de la operación
     */
    public double operations(char op, double num1, double num2){
        switch(op){
            case '+':
                return num1 + num2;
            case '-':
                return num1 - num2;
            case '*':
                return num1 * num2;
            case '/':
                if (num2 != 0){
                    return num1 / num2;
                }else{
                    return Double.MAX_VALUE;
                }
                
        }
        return 0.0;
    }
    
    /**
     * Función utilizada para comparar dos números
     * @param op La operación a implementar
     * @param num1 El primer operando
     * @param num2 El segundo operando
     * @return El resultado de la comparación
     */
    public boolean compares(char op, double num1, double num2){
        switch(op){
            case '<':
                return num1 < num2;
            case '>':
                return num1 > num2;
            case '=':
                return num1 == num2;
            case 'G':
                return num1 >= num2;
            case 'L':
                return num1 <= num2;
                
        }
        return false;
    }
    
    /**
     * 
     * @param in La clave que se busca en el HashMap
     * @return El valor de la clave buscada
     */
    public String searchInMap(String in){
        if(map.containsKey(in)){
            return map.get(in);
        }
        return in;
    }
    
    /**
     * 
     * @param key La clave en el HashMap
     * @param value El valor de la clave
     */
    public void setInMap(String key, String value){
        map.put(key, value);
    }
    
    /**
     * Limpia el HashMap
     */
    public void clear(){
        map.clear();
    }
}
